home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Group Library 1996 July / C-C++ Users Group Library July 1996.iso / listings / v_11_08 / twilling / jd2greg.c < prev   
Text File  |  1993-02-23  |  17KB  |  376 lines

  1.  
  2. /**********************(  GREGORIAN CALENDAR MODULE )**************************
  3.  
  4.  
  5. Routines for converting between Gregorian Calendar dates and Julian Days, for
  6. validating date input, for date arithmetic, and for learning day of the week
  7. and of the year.
  8.  
  9. (c) 1991, 1993 by Bob Twilling.  
  10. C Users Journal readers may use this code for any purpose.
  11.  
  12.  
  13.  
  14. /-------------------( Export to header file "JD2GREG.H" )--------------------*/
  15.  
  16. typedef struct { short   mo;
  17.                  short   dy;
  18.                  short   yr; }  MDY;
  19.  
  20. extern MDY *  jd2greg(     long  /*jd*/,    MDY * /*date*/  );
  21. extern long   greg2jd(     short /*month*/, short /*day*/  , short /*year*/ );
  22. extern long   ValidateDate(short /*month*/, short /*day*/  , short /*year*/ );
  23. extern long   DDays(       MDY * /*date1*/, MDY * /*date2*/ );
  24. extern MDY *  DatePlus(    MDY * /*date*/ , long  /*ddays*/, MDY * /*newdt*/ );
  25. extern int    DayOfWeek(   short /*month*/, short /*day*/  , short /*year*/ ,
  26.                            char * /*name*/ );
  27. extern int    DayOfYear(   short /*month*/, short /*day*/  , short /*year*/ );
  28.  
  29.  
  30.  
  31. /*=======================(  MONTH TO DAY-OF-YEAR  )===========================/
  32.  
  33. Given a month, calc day of year up until.  This is a macro used by the next 
  34. two functions, see discussion below for the logic.  Assumes rectified years 
  35. (March is month 1).  Can change the #if to 0 to save 24 bytes of near heap 
  36. at a slight speed cost.                                                     
  37.  
  38. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  39.  
  40. #if 1  
  41.    static short m2doy[] = 
  42.                 { 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 };
  43.    #define M2DOY(mo)  (m2doy[mo-1])
  44. #else
  45.    #define M2DOY(mo)  ((((mo) * 979) >> 5) - 30)
  46. #endif
  47.  
  48.  
  49.  
  50. /*=========================(  GREGORIAN TO JD  )==============================/
  51.  
  52. Given a month, day, and year, returns a long integer -- the Julian Day number
  53. at noon of that date.
  54.  
  55. ALGORITHM:
  56.  
  57. 1> Rectify the date so that the year begins on March 1. This simplifies
  58.    calculations by putting oddball leapdays last.
  59.  
  60. 2> Now calculate the day of year, e.g. Oct 28th is day number 242, Jan 3rd
  61.    is day number 309 (of the previous year! See above.)  A lookup table is
  62.    the fastest way to account for the varied number of days in a month, at
  63.    a cost of 24 bytes of near heap space.  Or we can use the formula:
  64.  
  65.          doy = ((mo * 979) / 32) - 30 + dy;
  66.  
  67.    March is the first month, remember.  There's no theory behind these magic
  68.    numbers -- a lot of other ones work too, but I like my 32 (a nice round
  69.    number).  979/32, or 367/12, or 30.6, all equal about the average days
  70.    per month, ignoring February.  The magic of integer arithmetic, and the
  71.    lucky fact that our 30-day months are well distributed, does the rest.
  72.  
  73. 3> Compute the number of days up to the beginning of the year using the
  74.    simple Gregorian formula:
  75.  
  76.          yrdays = (365 * yr) + (yr / 4) - (yr / 100) + (yr / 400);
  77.  
  78.    There are some nice round numbers in this formula too, begging to be
  79.    replaced by shifts.   
  80.  
  81. 4> Add the result of the last two steps.  Then add a constant for an origin
  82.    transform to the Julian Day system.
  83.  
  84. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  85.  
  86. extern long greg2jd( short mo, short dy, short yr )  {
  87.  
  88.    short lp;
  89.  
  90. if ((mo -= 2) <= 0) { mo += 12; yr--; }   // 1> Roll-under the date
  91. dy += M2DOY(mo);                          // 2> Calc day of year
  92. lp = yr >> 2;                             // 3> Add and subtract leap days
  93. dy += lp;
  94. lp /= 25;
  95. dy -= lp;
  96. dy += lp >> 2;
  97. return (long)yr * 365 + dy + 1721119;  }  // 4> One div, one mul!
  98.  
  99.  
  100.  
  101. /*=========================(  JD TO GREGORIAN  )==============================/
  102.  
  103. Given a long Julian Day and a pointer to a month-day-year structure, fills in
  104. that structure with the calendar date and returns the same pointer to it.
  105.  
  106. ALGORITHM:
  107.  
  108. 1> Transform the origin of the Julian Day to the start of some 400-year
  109.    period.  Because we are assuming a JD valid in the Gregorian Calendar we
  110.    subtract 2305507, so that day 1 is 1-Mar-1600.  As the routine is currently
  111.    written this gives an algorithm valid from 1200 AD.
  112.  
  113. 2> Subtract or divide out 400-year (146097 day) blocks, then 100-year (36524
  114.    day) blocks, noting the number of blocks removed.  Since 16-bit machines
  115.    and/or compilers do long division like paint dries, we use successive
  116.    subtraction -- sort of.  Actually, we subtract too far, then add back, for
  117.    a slight speed gain.  When we migrate to 32-bits, rewrite this part for
  118.    speed and comprehensibility.
  119.  
  120. 3> Divide out 4-year (1461 day) and 1-year (365 day) blocks.  Calculate the
  121.    calendar year from the number and size of blocks removed; the remainder is
  122.    the day of the year.  If the Julian Day represented a Feb 29, the algorithm
  123.    fails here because 1461/365 equals more than four.  Check and adjust for
  124.    this special case.
  125.  
  126. 4> Calculate the month of the year from the magic-number formula:
  127.  
  128.          mo = ((doy + 30) * 32) / 979; 
  129.  
  130.    Surprisingly, this is faster on a 386 than scanning a twelve member table.
  131.    Compute the day of the month by subtracting the days up until that month
  132.    using either the table or the formula declared in M2DOY() above.
  133.  
  134. 5> Roll over the month and year if we found a date in January or February.
  135.  
  136. 6> Be careful when changing this code:  146097/36524 and 1461/365 both equal
  137.    4, which blows up the algorithm on JD's representing Feb 29's.  As written,
  138.    we avoid the first by a tricky origin shift, the second by explicitly
  139.    limiting 1461/365 to a max of three.
  140.  
  141. /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  142. #include <stdlib.h>                          //for div()
  143. /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  144.  
  145. extern MDY * jd2greg( long jd, MDY * date )  {
  146.  
  147.    unsigned short x, y, d;
  148.    div_t          sd;                       
  149.  
  150. y = 1600; jd -= 2305507;                     // 1> new origin 0 = 2/29/1600
  151. while (jd > 0)  { y += 400; jd -= 146097; }  // 2> lop off 400-yr blocks
  152. do { y -= 100; } while ((jd += 36524) < 0);  //    and add back 100-yr ones
  153. d = (unsigned short)jd;                      // 3> within 16-bit range now
  154. x = d / 1461;                                //    pity ain't no ANSI udiv()
  155. y += x << 2;                                 //    note 4-yr blocks
  156. d -= x * 1461;                               //    and lop them off
  157. sd = div( d, 365);                           //    does % and / in same op
  158. date->yr = y + sd.quot;                      //    got years, provisionally
  159. d = ++sd.rem;                                // 4> day-of-year (base 1)
  160. if (sd.quot == 4)  { date->yr--; d = 366; }  //    case we hit a leap-year
  161. x = ((d + 30) << 5) / 979;                   //    x = month, March is 1
  162. date->dy = d - M2DOY(x);                     //    got day of month
  163. if ((x += 2) > 12)  { date->yr++; x -= 12; } // 5> roll around Jan and Feb 
  164. date->mo = x;                                //    got month
  165. return(date);  }
  166.  
  167.  
  168.  
  169. /*==========================(  VALIDATE DATE  )===============================/
  170.  
  171. Given a month, day, and year, returns a positive long integer representing the
  172. corresponding Julian Day at 12h UTC, unless the passed date is invalid in the
  173. Gregorian Calendar, in which case returns zero.  This somewhat-boolean retval
  174. will save the user another call to greg2jd() if the date does prove valid.
  175.  
  176. ALGORITHM:
  177.  
  178. 1> Convert the passed Gregorian date into its Julian Day.
  179.  
  180. 2> Check that the date is not earlier than 15 Oct 1582, the first day
  181.    Gregory's calendar was in use anywhere.  An earlier date would indicate
  182.    that the caller should have used Julian Calendar (Old Style) conversion
  183.    routines.
  184.  
  185. 3> Check that the date is not later than 28 Feb 4000.  On somewhat shaky
  186.    ground here.  If our planet's orbital speed doesn't change much, Greg's
  187.    calendar will lose a day every 3300 years.  I read somewhere of a proposal
  188.    for a Rev